%{open Ast 

let parse_error s = (* Called by the parser function on error *)
	print_endline s;
	flush stdout
	
let stab = ref []

let print_stab  s = print_endline "stab = "; 
	List.iter (fun v -> print_string (Ast.string_of_var v)) s

%}

%token TAB TABENDL LPAREN RPAREN LBRACK RBRACK COMMA COLON BAR IO
%token PLUS MINUS ASSIGN
%token AND OR LT GT LEQ GEQ EQ NEQ NOT
%token RETURN FOREACH IN WHILE IF
%token INT STR ARR 
%token <int> INTEGER
%token <string> ID
%token <string> STRING
%token EOF

%nonassoc END
%nonassoc TAB TABENDL
%right INT STR ARR 
%right ASSIGN
%right NOT IO
%left EQ NEQ
%left LT GT LEQ GEQ
%left PLUS MINUS
%left AND OR

%start program
%type <Ast.program> program 

%%

program:
	/* nothing */ { [], [] }
 	| program fdecl {  fst $1, ($2 :: snd $1) }

fdecl: /* function declaration */
	ID LPAREN params_opt RPAREN stmt_list
	{{fname = $1;
	 params = $3;
	 body = List.rev $5;
	 locals = List.rev !stab}}

params_opt: /* function parameters */
	/* nothing */ {stab:=[]; [] } /* arbitrary place to reset table for ea function */
	| params_list   { stab:=[]; List.rev $1 }

params_list: /* build function parameters as list */
	vdecl                   { [$1] }
	| params_list COMMA vdecl { $3 :: $1 } 

vdecl: 
	INT ID {{varType = Int; varName = $2;  dType = Int}}
	| STR ID {{varType = Str; varName = $2; dType = Str}}
	| ARR ID {{varType = Arr; varName = $2; dType = Arr}}
	| ID {{varType = Any; varName = $1; dType = Any}}

stmt_list:
	/* nothing */  { [] }
	| stmt_list TAB stmt { $3 :: $1 }
/*
stmt_tabopt:
	TABENDL {}    _tabopt 
	|  {}
*/
stmt:
	RETURN expr { Return($2) }
	| IF LPAREN expr RPAREN stmt_list %prec END  { If($3, Block(List.rev $5)) }
	| FOREACH LPAREN ID IN term RPAREN stmt_list %prec END { Foreach($3, $5, Block(List.rev $7)) }
	| WHILE LPAREN expr RPAREN stmt_list %prec END  { While($3, Block(List.rev $5)) }
	| IO term ASSIGN expr {Output($2, $4)}	/* id, string;  any */
	| IO ASSIGN expr {Output(Null, $3)}	/* any */
	| vdecl ASSIGN expr	{stab := $1::!stab; Assign($1, $3)}
	| vdecl			{stab := $1::!stab; Assign($1, Term(Null))}
	| ID LBRACK INTEGER RBRACK ASSIGN expr {SetAIndex(Id($1), Integer($3), $6)}

expr:
	term	{Term($1)}
	| LPAREN expr RPAREN{ $2 }
	| ID LPAREN args_opt RPAREN { Call($1, $3) }
	| ID LBRACK term COLON term RBRACK {Range(Id($1),$3,$5)} /* int,id; int,id */
	/* Binops */
	| expr PLUS   term { Binop($1, Add,   $3) } /* int, string, id, array */
	| expr MINUS  term { Binop($1, Sub,   $3) }  /* int, id */
	| expr EQ     term { Binop($1, Eq, $3) }  /* string, array, id, int */
	| expr NEQ    term { Binop($1, Neq,   $3) }  /* string, array, id, int */  
	| expr LT     term { Binop($1, Lt,  $3) }  /* int, id */
	| expr GT     term { Binop($1, Gt,  $3) }  /* int, id */
	| expr LEQ    term { Binop($1, Leq,   $3) }  /* int, id */
	| expr GEQ    term { Binop($1, Geq,   $3) }  /* int, id */
	| expr AND    term { Binop($1, And,   $3) }  /* int, id */
	| expr OR     term { Binop($1, Or,   $3) }  /* int, id */
	| ID LBRACK term RBRACK {Binop(Term(Id($1)), Vat, $3)}  /* int, id */
	/* Unops */
	| NOT expr		{Unop($2,Not)} /* resolves to int */
	| IO term			{Unop(Term($2),In)}  /* string, id, int */
	| BAR term BAR		{Unop(Term($2),Len)}

term:
	ID			{ Id($1) }
	| INTEGER		{ Integer($1) }
	| STRING		{ String($1) }
	| LBRACK term_opt RBRACK{ Array($2) } /* int, vars, strings */

args_opt:
	/* nothing */ { [] }
	| args_list  { List.rev $1 }

args_list:
	expr                    { [$1] }
	| args_list COMMA expr { $3 :: $1 }

term_opt:
	/* nothing */ { [] }
	| term_list  { List.rev $1 }

term_list:
	term                    { [$1] }
	| term_list COMMA term { $3 :: $1 }
